Vlakna - zacatky
Otázka od: Ing. Jiri Sokol
3. 9. 2004 11:24
Ahoj panove!
Muj problem je nasledujici. Prebral jsem aplikaci, nechtelo se mi ji predelavat
odznova a ted porad narazim na nejake problemy.
Co bych momentalne potreboval vyresit je tohle:
- hlavni aplikace slouzici k obsluze zprav, zobrazovani nejkych dilcich udaju
atd.
- jeden nezavisly thread - ktery se bude spoustet v nejakem casovem intervalu -
sam si zjisti jestli ma neco udelat provede to - do te doby se nesmi spustit
dalsi thread...
- druhy thread - opet bude spusten po nejakem casovem intervalu - plati to
same, co v prvnim pripade - nemusi delat nic, ale muzou mu operace zabrat i
nekolik sekund - nesmi dojit k opetovnemu spusteni dalsiho threadu
to, ze nesmi dojit k opetovnemu spusteni jednotlivych threadu plati i v
kombinaci mezi nimi - zkratka, kdyz uz nejaky thread bezi, nesmi bezet dalsi.
Muzete mi,prosim, pomoct vytvorit primitivni zakladni aplikaci, ktera bude
splnovat vyse uvedene? Muzem si odpoustit deklaraci fromu atd., ale zajimalo by
me, kdy vytvorit thread, jestli mam volat execute z nejakeho timeru, co na to
synchronizace atd.
Verim, ze lidem, co budou po me zacinat s thready to hodne pomuze.
Vlakno1, Vlakno2: TThread;
procedure TForm.Create(Sender:TObject);
begin
Vlakno1:=TThread.Create(Application.Handle);
Valkno2:=TThread.Create(Application.Handle);
end;
Procedure TForm.Timer1OnTimer(Sender: TObject);
begin
if nebezi vlakno2 a nebezi ani vlakno 1 then
Vlakno1.Execute;
end;
Procedure TForm.Timer1OnTimer(Sender: TObject);
begin
if nebezi vlakno2 a nebezi ani vlakno 1 then
Vlakno2.Execute;
end;
procedure TForm.Destroy(Sender: TObject);
begin
Vlakno1.Terminate;
Vlakno1.WaitFor;
Vlakno2.Terminate;
Vlakno2.WaitFor;
end;
je to OK?
a co dal? co se dela v metode Create toho vlakna? Jak se synchronizuje -
potreboval bych z toho vlakna hazet neco do Edit1.Text na Form. V Create mam
vytvorit samotnou TIBDatabase + TIBTransaction + TIBQuery? Muzu nejak kopirovat
nastaveni hodnot z komponent, ktere to uz maji nastavene v DataModulu? Jak?
(mam namysli v souvislosti se synchronizaci - pochopitelne, ze normalne - mimo
vlakno - to umim)
Musim si dat jeste bacha na neco?
Diky moc vsem a predem!
Jirka
--------------------------------------------------
Ing. Jiri Sokol; jiri.sokol@seznam.cz; 972 231 187
D6Prof+SP3; WinXPProf+SP1; FB 1.5.0
programator amater
Odpovedá: Jiri Virt
3. 9. 2004 12:22
Tohle je v helpu D5. mohlo by to byt to co potrebujes?
unit Pg1;
interface
uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
StdCtrls, ComCtrls, ExtCtrls, Pg2;
const
WM_ThreadDoneMsg = WM_User + 8;
type
TForm1 = class(TForm)
ProgressBar1: TProgressBar;
ProgressBar2: TProgressBar;
Button1: TButton;
Button2: TButton;
TrackBar1: TTrackBar;
TrackBar2: TTrackBar;
Bevel1: TBevel;
Bevel2: TBevel;
Label1: TLabel;
Label2: TLabel;
Button3: TButton;
Button4: TButton;
procedure Button1Click(Sender: TObject);
procedure Button2Click(Sender: TObject);
procedure Button3Click(Sender: TObject);
procedure Button4Click(Sender: TObject);
procedure FormCreate(Sender: TObject);
procedure TrackBar1Change(Sender: TObject);
procedure TrackBar2Change(Sender: TObject);
procedure FormDestroy(Sender: TObject);
private
{ Private declarations }
MyThread1 : TMyThread; // thread number 1
MyThread2 : TMyThread; // thread number 2
Thread1Active : boolean; // used to test if thread 1 is active
Thread2Active : boolean; // used to test if thread 2 is active
procedure ThreadDone(var AMessage : TMessage); message WM_ThreadDoneMsg;
// Message to be sent back from thread when its done
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
{$R *.DFM}
procedure TForm1.Button1Click(Sender: TObject); // Create Thread 1
{ The thread will destroy iteself when it is done executing because
FreeOnTerminate is set to true.
The first paramter is the priority, and the second is the progressbar to
update.
}
begin
if (MyThread1 = nil) or (Thread1Active = false) then // make sure its not
already running
begin
MyThread1 := TMyThread.CreateIt(TrackBar1.Position, ProgressBar1);
Thread1Active := true;
end
else
ShowMessage('Thread still executing');
end;
procedure TForm1.Button2Click(Sender: TObject); // Create Thread 2
begin
if (MyThread2 = nil) or (Thread2Active = false) then // make sure its
not already running
begin
MyThread2 := TMyThread.CreateIt(TrackBar2.Position, ProgressBar2);
Thread2Active := true;
end
else
ShowMessage('Thread still executing');
end;
procedure TForm1.Button3Click(Sender: TObject); // Terminate Thread 1
begin
if (MyThread1 <> nil) and (Thread1Active = true) then // check to see if
it is running
MyThread1.Terminate
else
ShowMessage('Thread not started');
end;
procedure TForm1.Button4Click(Sender: TObject); // Terminate Thread 2
begin
if (MyThread2 <> nil) and (Thread2Active = true) then // check to see if
it is running
MyThread2.Terminate
else
ShowMessage('Thread not started');
end;
procedure TForm1.ThreadDone(var AMessage: TMessage); // keep track of when
and which thread is done executing
begin
if ((MyThread1 <> nil) and (MyThread1.ThreadID =
cardinal(AMessage.WParam))) then
begin
Thread1Active := false;
end;
if ((MyThread2 <> nil) and (MyThread2.ThreadID =
cardinal(AMessage.WParam))) then
begin
Thread2Active := false;
end;
end;
procedure TForm1.FormCreate(Sender: TObject); // initialize to zero
begin
Thread1Active := false;
Thread2Active := false;
end;
procedure TForm1.TrackBar1Change(Sender: TObject); // set Thread 1 Priority
begin
if (MyThread1 <> nil) and (Thread1Active = true) then
MyThread1.priority := TThreadPriority(TrackBar1.Position);
end;
procedure TForm1.TrackBar2Change(Sender: TObject); // set Thread 2 Priority
begin
if (MyThread2 <> nil) and (Thread2Active = true) then
MyThread2.priority := TThreadPriority(TrackBar2.Position);
end;
procedure TForm1.FormDestroy(Sender: TObject); // Terminate any threads
still running
begin
if (MyThread1 <> nil) and (Thread1Active = true) then
begin
MyThread1.Terminate;
MyThread1.WaitFor; // wait for it to terminate
end;
if (MyThread2 <> nil) and (Thread2Active = true) then
begin
MyThread2.Terminate;
MyThread2.WaitFor;
end;
end;
end.
Odpovedá: Ing. Jiri Sokol
3. 9. 2004 12:31
> [mailto:delphi-l-owner@clexpert.cz] On Behalf Of Jiri Virt
> Sent: Friday, September 03, 2004 12:14 PM
>
> Tohle je v helpu D5. mohlo by to byt to co potrebujes?
>
Ahoj.
Je to asi z vetsi casti to co potrebuju. Nicmene tam nebyly jeste odpovedi na
otazky synchronizace a dalsi:
- co se dela v metode Create toho vlakna?
- jak se spravne synchronizuje - potreboval bych z toho vlakna hazet neco do
Edit1.Text na Form - metodu synchronize volam presne kdy?
- v Create vlakna mam vytvorit samotnou TIBDatabase + TIBTransaction +
TIBQuery? Muzu nejak kopirovat nastaveni hodnot z komponent, ktere to uz maji
nastavene v DataModulu? Jak?
- jak je to s behem vlakna? Kdyz zavolam jeho procedure Execute, tak bezi jako
klasicky kod a nebo po dobehnuti do konce jede aut. od zacatku? (to je asi
blbost, co?)
- musim si dat jeste bacha na neco?
Diky
Jirka
--------------------------------------------------
Ing. Jiri Sokol; jiri.sokol@seznam.cz; 972 231 187
D6Prof+SP3; WinXPProf+SP1; FB 1.5.0
programator amater
Odpovedá: Jiri Virt
3. 9. 2004 12:39
>- co se dela v metode Create toho vlakna?
var
SecondProcess TMyThread; { TMyThread is a custom descendant of TThread }
begin
SecondProcess := TMyThread.Create(True); { create suspended -
secondprocess does not run yet }
SecondProcess.Priority = tpLower; { set the priority to lower than normal
}
SecondProcess.Resume; { now run the thread }
end;
> - jak se spravne synchronizuje - potreboval bych z toho vlakna hazet neco
do Edit1.Text na Form - metodu synchronize volam presne kdy?
procedure TMyThread.PushTheButton;
begin
Button1.Click();
end;
procedure TMyThread.Execute;
begin
...
Synchronize(PushTheButton);
...
end;
>- v Create vlakna mam vytvorit samotnou TIBDatabase + TIBTransaction +
TIBQuery? Muzu nejak kopirovat nastaveni hodnot z komponent, ktere to uz
maji nastavene v DataModulu? Jak?
Muzes zkopirovat
procedure TMyThread.Execute;
begin
...
IBThDatabase := MyIBDatabase;
...
end;
>- jak je to s behem vlakna? Kdyz zavolam jeho procedure Execute, tak bezi
jako klasicky kod a nebo po dobehnuti do konce jede aut. od zacatku? (to je
asi blbost, co?)
Ne .. Dojede a pokud to nemas v while nebo for cyklu, tak se I thread ukonci
Vsech to mam vytahnute z helpu, mrkni tam, je tam u kazdeho vyrazu k threadu
ukazka (exampl) a pekne vysvetleno
Jirka Virt
Odpovedá: Petr Fejfar
3. 9. 2004 12:13
Ing. Jiri Sokol wrote:
> ale zajimalo by me, kdy vytvorit thread, jestli mam volat execute z
> nejakeho timeru, co na to synchronizace atd.
> Verim, ze lidem, co budou po me zacinat s thready to hodne pomuze.
Nejdriv by to chtelo upresnit zadani: pises, co se nesmi, ale nepises, co
se ma
> if nebezi vlakno2 a nebezi ani vlakno 1 then
> Vlakno1.Execute;
Kdyz vezmu tuhle podminku, tak sice rika, ze thready nepobezi soucase,
ale na druhou stranu se muze stat, ze perioda spousteni threadu muze znacne
"kulhat" - hypoteticky se nektery ze threadu nemusi spoustet vubec. Takze je
nutne specifikovat, jestli ti to tak staci nebo jestli pozadujes, aby
jakmile jeden thread skonci se spustil ten druhy, i kdyz se zpozdenim apod.
> Vlakno1.Execute;
Tohle je jedna z hrubek, ktera se casto dela - kdybys to napsal takto, tak
ti kod v metode Execute stale pobezi v contextu volajiciho tj. hlavniho
threadu.
Thread se nespousti volanim jeho metody execute, ale rozebehne se bud
automaticky, pokud konstruktoru nedas parametr CreateSuspended a nebo
explicitne volanim metody Resume.
> jak je to s behem vlakna? Kdyz zavolam jeho procedure Execute, tak
> bezi jako klasicky kod a nebo po dobehnuti do konce jede aut. od
> zacatku? (to je asi blbost, co?)
To zalezi prave na zadani - casto se thread vytvori a spusti a pak v tele
metody Execute ceka pomoci funkce WaitForSingleObject/WaitForMultipleObjects
na stav nejakeho synchronizacniho prostredku (event, semaphore apod), ktery
ho "vzbudi", thread udela co ma a vrati se do stavu cekani - tedy obsahuje
cyklus. Takto se typicky resi akce, ktere se periodicky opakuji.
Jednorazeove ulohy se zpravidla resi vytvorenim threadu a jeho zrusenim po
provedeni ulohy.
HTH, pf
Odpovedá: Ing. Jiri Sokol
3. 9. 2004 12:46
> Od: Petr Fejfar <development@callnet.cz>
> Datum: 03.09.2004 13:14:45
> > if nebezi vlakno2 a nebezi ani vlakno 1 then
> > Vlakno1.Execute;
>
> Kdyz vezmu tuhle podminku, tak sice rika, ze thready nepobezi soucase,
> ale na druhou stranu se muze stat, ze perioda spousteni threadu muze znacne
> "kulhat" - hypoteticky se nektery ze threadu nemusi spoustet vubec. Takze je
> nutne specifikovat, jestli ti to tak staci nebo jestli pozadujes, aby
> jakmile jeden thread skonci se spustil ten druhy, i kdyz se zpozdenim apod.
Na tom je hodne pravdy - budu si asi muset udelat nejakou "frontu"
udalosti... aby nebezelo jenom jedno to vlakno...
> To zalezi prave na zadani - casto se thread vytvori a spusti a pak v tele
> metody Execute ceka pomoci funkce WaitForSingleObject/WaitForMultipleObjects
> na stav nejakeho synchronizacniho prostredku (event, semaphore apod), ktery
> ho "vzbudi", thread udela co ma a vrati se do stavu cekani - tedy obsahuje
> cyklus. Takto se typicky resi akce, ktere se periodicky opakuji.
Tohle je zrejme presne to, co potrebuju. Nebyl by ukazkovy prikladek? No snda
nechci moc... nevim... Stejne diky aspon za nakopnuti, timhle se budu chtit
ubirat...
Jirka
--------------------------------------------------
Ing. Jiri Sokol; jiri.sokol@seznam.cz; 972 231 187
D6Prof+SP3; WinXPProf+SP1; FB 1.5.0
programator amater
Odpovedá: Ondrej Kelle
3. 9. 2004 12:49
Borland pred nejakou dobou vyhlasil sutaz o multithreaded aplikaciach,
vysledky tej sutaze najdes tu:
http://bdn.borland.com/article/0,1410,29786,00.html
Myslim, ze je to celkom dobry material na studium.
Jeden z ucastnikov napisal aj clanok, kde svoj (vitazny) prispevok popisuje:
http://www.thedelphimagazine.com/samples/1712/1712.htm
Je v nom zaujimave to, ako na updatovanie VCL v hlavnom threade nepouziva
Synchronize, ale PostMessage.
HTH
TOndrej
Odpovedá: Petr Fejfar
3. 9. 2004 12:50
Ing. Jiri Sokol wrote:
> Tohle je zrejme presne to, co potrebuju. Nebyl by ukazkovy prikladek?
> No snda nechci moc... nevim... Stejne diky aspon za nakopnuti, timhle
> se budu chtit ubirat... Jirka
Kdyz tak vecer (v noci), ted musim odjet.
pf
Odpovedá: Petr Fejfar
3. 9. 2004 23:13
Ing. Jiri Sokol wrote:
> Tohle je zrejme presne to, co potrebuju. Nebyl by ukazkovy prikladek?
Vidim, ze se k tomu nikdo nehlasi:
1. Pro tvuj problem bych volil thread, ktery bude cekat na zadost o
zpracovani.
- zadost bych implementoval semaphorem, ktery by byl atributem threadu
(semafor resi tu "frontu" pozadavku)
- exklusivitu behu threadu bych resil mutexem - protoze se nekdy muze
vyskytnout pozadavak na exklusivitu vzhledem k jine aplikaci
(treba k instalacnimu scriptu), pouzil bych obecnejsi pojmenovany mutex
jehoz jmeno bych predaval jako argument konstruktoru.
- Thread bych prerusoval/ukoncoval "ciste" pomoci dedikovane event,
ktera by byla atributem threadu
2. Protoze mas dva ruzne thready, ktere ale budou mit radu spolecnych
vlastnosti,
je na miste zapremyslet o taxonomii threadu, asi nejjednodussi by byla:
- TAbstractThread(TThread)
- TExlusiveThread(TAbstractThread)
- TIndependentThread(TAbstractThread)
Nastinil jsem ti skelet threadu v duchu, v jakem thready pouzivame, ale
zamerne jsem sloucil podstatne vlastnosti z hierarchie trid do jedine tridy.
Sam si to uz jiste rozhodis do trid podle vlastnich potreb.
3. V aplikaci vytvoris instanci threadu a zase ji explicitne zrusis
(preferuji tohle reseni s plnou kontrolou zivota threadu, protoze pri
automatickem ruseni threadu po dobehnuti metody Execute ti zbyde v nejake
promenne reference na neplatny objekt, coz muze vest k docela neprijemnym
chybam. Rovnez tak automaticke rozebehnuti threadu muze cinit zaludne
problemy). Priklad:
const
cMTX_ExlusiveThread =
'..._worldwide_unique_mutex_name_as_global_literal...';
Thread1 := TExclusiveThread.Create(cMTX_ExlusiveThread);
Thread1.Resume;
...
Thread1.NewRequest;
...
Thread1.Kill(INFINITE);
Thread1.Free;
4. Nize je skelet - psal jsem ho z hlavy, takze jsem se mohl nekde upsat,
ale podstatne chyby by v tom snad byt nemely. Vsimni si
inicializace/finalizace
threadu, ktera bezi jiz v kontextu vlastniho threadu a metody Kill,
ktera slouzi k regulernimu ukonceni threadu (kdyby Borland udelal metodu
Terminate virtualni, overridnula by se ta, ovsem neudelal, takze je nutne to
delat jinak). Cekani je dvoji: prvni WaitFor.... bez timeoutu ceka na novy
pozadavek, druhe vnorene volani na mutex s timeoutem tj. dobu, do kdy
museji skoncit ostatni thready a musi se zahajit zpracovani pozadavku. Psane
je to tak, aby se snadno daly pridavat dalsi handles, na ktere ceka:
const
maxExclusivityTimeout = 60*1000; // max. time [ms] the mutex has to be
grabbed
type
eWaitHandles = (ewhKill,ewhRequest);
eGrabHandles = (eghKill,eghExclusivity);
type
xInternal = class(Exception);
xFatal = class(Exception);
type
TExclusiveThread = class(TThread)
private
procedure __Body;
procedure __ProcessRequest;
protected
FRunning: boolean;
FKillEvent: THandle;
FWaitHandles: array[eWaitHandles] of THandle;
FGrabHandles: array[eGrabHandles] of THandle;
FMutexName: ANSIString;
protected
procedure _Init; virtual;
procedure _Done; virtual;
function _GrabMutex(ATimeout:dword): boolean;
public
constructor Create(const AMutexName:ANSIString);
procedure Execute; override;
procedure NewRequest;
function Kill(ATimeout:dword): boolean;
public
property Running: boolean read FRunning;
end;
constructor TExclusiveThread.Create(const AMutexName:ANSIString);
begin
inherited Create(TRUE);
FreeOnTerminate := FALSE;
FMutexName := AMutexName;
end;
procedure TExclusiveThread.Execute;
begin
_Init;
FRunning := TRUE;
try
while not Terminated do
try
__Body;
except
on E:xFatal do
raise;
end;
finally
FRunning := FALSE;
_Done;
end;
end;
procedure TExclusiveThread.NewRequest;
begin
if FWaitHandles[ewhRequest]<>0 then
ReleaseSemaphore(FWaitHandles[ewhRequest],1,nil);
end;
function TExclusiveThread.Kill(ATimeout:dword): boolean;
begin
if FKillEvent<>0 then
SetEvent(FKillEvent);
Terminate;
case WaitForSingleObject(Handle,ATimeout) of
WAIT_TIMEOUT: Result := FALSE;
else Result := TRUE;
end;
end;
procedure TExclusiveThread._Init;
begin
FKillEvent := CreateEvent(nil,bManualReset,bNonSignaled,'');
if FKillEvent=0 then
RaiseLastOSError;
//
FWaitHandles[ewhKill] := FKillEvent;
FWaitHandles[ewhRequest] := CreateSemaphore(nil,0,maxint,'');
if FWaitHandles[ewhRequest]=0 then
RaiseLastOSError;
//
FGrabHandles[eghKill] := FKillEvent;
FGrabHandles[eghExclusivity] := CreateMutex(nil,FALSE,pChar(FMutexName));
if FGrabHandles[eghExclusivity]=0 then
RaiseLastOSError;
end;
procedure TExclusiveThread.__Body;
var
WxRes: dword;
begin
WxRes :=
WaitForMultipleObjects(ord(high(FWaitHandles))-ord(low(FWaitHandles))+1,addr
(FWaitHandles),FALSE,INFINITE);
case WxRes of
WAIT_FAILED:
begin
RaiseLastOSError;
end;
WAIT_TIMEOUT:
begin
raise xInternal.Create('....'); // may not occur
end;
WAIT_OBJECT_0+ord(ewhKill):
begin
exit;
end;
WAIT_OBJECT_0+ord(ewhRequest):
begin
if _GrabMutex(maxExclusivityTimeout) then
try
__ProcessRequest;
finally
Win32Check(ReleaseMutex(FGrabHandles[eghExclusivity]));
end
else if not Terminated then
raise xInternal.Create('.....');
end;
WAIT_ABANDONED_0..WAIT_ABANDONED_0+ord(high(FWaitHandles)):
raise xInternal.CreateFmt('....',[WxRes-WAIT_ABANDONED_0]);
else
raise xInternal.CreateFmt('....',[WxRes]);
end;
end;
function TExclusiveThread._GrabMutex(ATimeout:dword): boolean;
var
WxRes: dword;
begin
Result := FALSE;
WxRes :=
WaitForMultipleObjects(ord(high(FGrabHandles))-ord(low(FGrabHandles))+1,addr
(FGrabHandles),FALSE,ATimeout);
case WxRes of
WAIT_FAILED:
RaiseLastOSError;
WAIT_TIMEOUT:
;
WAIT_OBJECT_0+ord(eghKill):
;
WAIT_OBJECT_0+ord(eghExclusivity):
Result := TRUE;
WAIT_ABANDONED_0..WAIT_ABANDONED_0+ord(high(FGrabHandles)):
raise xInternal.CreateFmt('....',[WxRes-WAIT_ABANDONED_0]);
else
raise xInternal.CreateFmt('...');[WxRes]);
end;
end;
procedure TExclusiveThread._Done;
begin
if FKillEvent<>0 then
CloseHandle(FKillEvent);
//
if FWaitHandles[ewhRequest]<>0 then
CloseHandle(FWaitHandles[ewhRequest]);
//
if FGrabHandles[eghExclusivity]<>0 then
CloseHandle(FGrabHandles[eghExclusivity]);
end;
procedure TExclusiveThread.__ProcessRequest;
begin
end;
HTH, pf